﻿using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using JsonFx.Json;

/// <summary>
/// 네트워크가 작동되는 메인 로직
/// LSH/2013.11.21
/// </summary>
public abstract class NetworkProcessMain : MonoBehaviour {
	// public Member
    public bool                                 ParallerTransmit = false;
    public float                                HttpResponseWaitTime = 5.0f;

	// private Member
	protected Queue<NetMsg> 	                mQueSendMsg;
	protected string			                mUrl;
	protected short 			                mTransmitStatus;
    protected short                             mPause = 1;

    //private string                       mSessionCookie;
	private string                       mSessionID;
    private byte[] key;
    private byte[] Key { get { return AES.Instance.Decrypt(key); } set { key = AES.Instance.Encrypt(value); } }

    private static readonly bool httppost = false;
    private static readonly string mimeTypeCrypto = "application/octet-stream";

    /// <summary>
    /// 네트워크 초기화 구문
    /// </summary>
	public void Initialize() {
        mQueSendMsg = new Queue<NetMsg>();

        //mSessionCookie = null;
		mSessionID = null;

        mTransmitStatus = 0;
	}

	/// <summary>
	/// 네트워크가 연결될 URL 설정
	/// </summary>
	/// <param name="strUrl"></param>
	public void SetConnectIPPort(string strUrl) {
        this.mUrl = strUrl;

	}

    /// <summary>
    /// 네트워크 패킷 Queue에 패킷 추가
    /// </summary>
    /// <param name="Msg"></param>
    protected void PushPacketToTrasmitQue(NetMsg Msg) {
		mQueSendMsg.Enqueue(Msg);
	}


    /// <summary>
    /// 네트워크 패킷 Queue에서 패킷 빼오기.
    /// </summary>
    /// <returns></returns>
    protected NetMsg PopPacketToTransmitQue() {
        return mQueSendMsg.Dequeue();
    }

    /// <summary>
    /// 패킷 전송이 끝났을 경우.
    /// </summary>
    /// <returns></returns>
    protected NetMsg TransmitCompleteTheProcess()
    {
        mTransmitStatus--;
        return PopPacketToTransmitQue();
    }

    /// <summary>
    /// 직렬화 인가, 병렬화 인가?
    /// </summary>
    /// <returns></returns>
    bool IsSerialOrParaller() {
        if( mQueSendMsg.Count > 0 )
        {
            if (mTransmitStatus <= 0 && mPause >= 0)
                return true;
        }

        return false;
    }

    void Update() {
        if ( IsSerialOrParaller() ) {
            ProcessTransmitFromQue();
        }
    }

    /// <summary>
    /// 큐에서 패킷을 Peek한 후에 메세지 전송.
    /// </summary>
    /// <param name="msg"></param>
    private void SendPacketFromQueuePeek()
    {
        // 재전송을 위해서 빼지말고 가져오기만 한다.
        if (mQueSendMsg.Count <= 0)
            return;

        NetMsg msg = mQueSendMsg.Peek();
        if (httppost)
            WWWPostTransmit(msg.MsgID, msg);
        else
            WWWTransmit(FieldCombineComma(msg), msg);
    }

    /// <summary>
    /// GET 통신
    /// </summary>
    /// <param name="strMsg"></param>
    /// <param name="msg"></param>
    /// <returns></returns>
	protected void WWWTransmit(string strMsg, NetMsg msg)
    {
		StringBuilder sb = new StringBuilder();
		sb.Append(mUrl);
		sb.Append(strMsg);
		
		string strTransmitUrl = sb.ToString();
		Debug.Log ("TransmitUrl : " + strTransmitUrl);

        byte[] data = Common.Encoding.GetBytes(strMsg);
        string checkStr = null;
        //if (msg.Crypto && key != null)
        if (key != null)    //TODO
            checkStr = new HMAC(Key).ComputeHashToBase64(data);

        WWW www = new WWW(strTransmitUrl, null, MakeHeader(checkStr));
        
        StartCoroutine(WWWProcess(www, msg));
	}

    /// <summary>
    /// POST 통신
    /// </summary>
    /// <param name="strfield"></param>
    /// <param name="msg"></param>
    /// <returns></returns>
    protected void WWWPostTransmit(string strfield, NetMsg msg)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(mUrl);
        sb.Append(strfield);
        string strTransmitUrl = sb.ToString();
        Debug.Log("TransmitUrl : " + strTransmitUrl);

        //WWWForm form = new WWWForm();        
        //foreach (KeyValuePair<string, string> post_arg in msg.GetDicField()) 
        //{ 
        //    form.AddField(post_arg.Key, post_arg.Value); 
        //}

        byte[] data = msg.GetQueryStringBytes();
        string checkStr = null;
        //if (msg.Crypto && key != null)
        if (key != null)    //TODO
            checkStr = new HMAC(Key).ComputeHashToBase64(data);

        WWW www = new WWW(strTransmitUrl, data, MakeHeader(checkStr));

        StartCoroutine(WWWProcess(www, msg));
    }

    /// <summary>
    /// 패킷 전송.
    /// </summary>
    /// <param name="www"></param>
    /// <param name="msg"></param>
    /// <returns></returns>
    protected IEnumerator WWWProcess(WWW www, NetMsg msg)
    {
        WaitServerResponse();
        yield return www;
        ReceiveServerResponse();

        if (!string.IsNullOrEmpty(www.error))
        {
            OutputWWWError(www.error, msg);

            yield break;
        }

        //old: use cookie
        //if( www.responseHeaders.ContainsKey("SET-COOKIE") )
        //{
        //    string v = www.responseHeaders["SET-COOKIE"];
        //    mSessionCookie = v;
        //}

        //new: use custom header
		if (www.responseHeaders.ContainsKey("GSESSIONID"))
		{
			string v = www.responseHeaders["GSESSIONID"];
			mSessionID = v;
		}

        if (www.responseHeaders.ContainsKey("S"))
        {
            string v = www.responseHeaders["S"];
            byte[] bytes = Common.Encoding.GetBytes(v);
            Key = bytes;
        }

        //if( mSessionCookie == null )
        //{
        //    Debug.LogError("Not Exist Cookie Session");
        //    OutputWWWError(www.error, msg);
            
        //    yield break;
        //}


        TransmitCompleteTheProcess();

        ParseResponseStringToStructure(www.text, msg);
    }


    /// <summary>
    /// HTTP 에러시, 호출되는 함수.
    /// </summary>
    /// <param name="strError"></param>
    /// <param name="msg"></param>
    protected abstract void OutputWWWError(string strError, NetMsg msg);

    /// <summary>
    /// 서버에서 받은 메세지가 에러 코드일때.
    /// </summary>
    /// <param name="nErrorCode"></param>
    /// <param name="msg"></param>
    protected abstract void OutputServerError(int nErrorCode);

    /// <summary>
    /// 서버 응답을 기다리기 시작하는 시점에 실행되는 함수.
    /// </summary>
    protected abstract void WaitServerResponse();

    /// <summary>
    /// 서버 응답을 받고 난후 실행되는 함수
    /// </summary>
    protected abstract void ReceiveServerResponse();

    /// <summary>
    /// 받은 메세제를 재전송.
    /// </summary>
    /// <param name="msg"></param>
    protected void RetryTransmit()
    {
        SendPacketFromQueuePeek();
    }

    /// <summary>
    /// 받은 데이터를 모델에 맞춰서 JSON 파싱해준다.
    /// </summary>
    /// <param name="stData"></param>
    /// <param name="msg"></param>

	/// (Before Version) ParseResponseStringToStructure
	/// 
	/*
    protected void ParseResponseStringToStructure(string stData, NetMsg msg)
    {
    	//Type tp = GetResponseModelType(msg.MsgID);
		//var genericResponseModel = typeof(ResponseModel<>);
		//var specificResponseType = genericResponseModel.MakeGenericType(tp);

		Debug.Log(stData);

        object response = Convert.ChangeType(JsonReader.Deserialize(stData, msg.Type), msg.Type);

		if (response != null)
		{
			int ecode = (int)response.GetType().GetField("ecode").GetValue(response);
            if (ecode != 0 && msg.IsShowError)
            {
                if (msg.CallbackErr != null)
                {
                    Type callbackType = typeof(System.Action<>).MakeGenericType(msg.Type);
                    object onCallback = Convert.ChangeType(msg.CallbackErr, callbackType);

                    callbackType.InvokeMember("Invoke", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, onCallback, new object[] { response });
                }
                else
                {
                    OutputServerError(ecode);
                }
            }
		}

		//if( msg.CallbackAck != null )
		//	msg.CallbackAck(response);

		if (msg.CallbackAck != null)
		{
			Type callbackType = typeof(System.Action<>).MakeGenericType(msg.Type);
			object onCallback = Convert.ChangeType(msg.CallbackAck, callbackType);

			callbackType.InvokeMember("Invoke", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, onCallback, new object[] { response });
		}
    }
	*/

	protected void ParseResponseStringToStructure(string stData, NetMsg msg)
	{
		Debug.Log(stData);

        //TODO:TEMP-Deserialize 에러시 임시 처리(140729 hikim)
        object o = null;
        try
        {
            o = JsonReader.Deserialize(stData, msg.Type);
        }
        catch (Exception ex)
        {
			// TODO: 예외 관련 메시지로 나오도록 수정해 둠 (141010 HyeonGyu Yu)
			PopupUtility.OneButtonErrorPopup(ex.Message.ToString());
            return;
        }
		object response = Convert.ChangeType(o, msg.Type);

		// 1 : 정상적인 응답 (Response가 null이 아닐 경우)
		if (response != null)
		{          
            int ecode = (int)response.GetType().GetField("ecode").GetValue(response);                
            

            // 1-A : 응답 결과에 Error Code가 존재할 경우
            if (ecode != 0 && msg.IsShowError)
            {
                if (msg.CallbackErr != null)
                {
                    Type callbackType = typeof(System.Action<>).MakeGenericType(msg.Type);
                    object onCallback = Convert.ChangeType(msg.CallbackErr, callbackType);

                    callbackType.InvokeMember("Invoke", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, onCallback, new object[] { response });
                }
                else
                {
                    OutputServerError(ecode);
                }
            }

            // 1-B : 응답 결과 값도 정상적임
            else
            {
                if (msg.CallbackAck != null)
                {
                    Type callbackType = typeof(System.Action<>).MakeGenericType(msg.Type);
                    object onCallback = Convert.ChangeType(msg.CallbackAck, callbackType);

                    callbackType.InvokeMember("Invoke", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.InvokeMethod, null, onCallback, new object[] { response });
                }
            }
		}
		// 2 : 비정상적인 응답 (Response가 null)
		else
		{
			// 비정상 응답에 대한 처리 부분 ...
		}

	}

    /// <summary>
    /// 네트워크 Queue에서 전송할 패킷을 차례대로 빼온다.
    /// </summary>
	protected void ProcessTransmitFromQue() {
		mTransmitStatus++;
	
        SendPacketFromQueuePeek();
	}
	
    /// <summary>
    /// 전송할 데이터를 URL Field에 추가시켜준다.
    /// </summary>
    /// <param name="msg"></param>
    /// <returns></returns>
	static public string FieldCombineComma(NetMsg msg) {
		StringBuilder sb = new StringBuilder();
        sb.Append(msg.MsgID);
        sb.Append("?");
        foreach (KeyValuePair<string, string> Field in msg.GetDicField() )
        {
            sb.Append("&");
            sb.Append(Field.Key);
            sb.Append("=");
            sb.Append(Field.Value);
        }
		
		return sb.ToString();
	}

    protected Hashtable MakeHeader(string checkStr)
    {
        Hashtable ht = new Hashtable();

		//old: use cookie
        //if (!Application.isWebPlayer && mSessionCookie != null) {
        //    ht["Cookie"] = mSessionCookie;
        //}

		//new: use custom header
		if (mSessionID != null) {
			ht["GSESSIONID"] = mSessionID;
		}

        if (checkStr != null) {
			ht["C"] = checkStr;
		}

        return ht;
    }
	
	

}
